/* Pontificia Universidad Javeriana - Facultad de Ingenier�a   */
package BESA.Agent;

/* Proyecto Arquitectura SMA - Plataforma BESA - 2002          */
import java.io.Serializable;
import java.util.ArrayList;

import BESA.Agent.Event.DataBESA;
import BESA.Agent.Event.EventBESA;
import BESA.Exception.ExceptionBESA;
import BESA.SystemLevel.AdmBESA;
import BESA.SystemLevel.Directory.AgHandler;
import BESA.SystemLevel.Directory.AgHandlerBESA;
import BESA.util.ReportBESA;

@SuppressWarnings("all")
abstract public class AgentBESA implements Serializable {

    private static final long serialVersionUID = 2978316073669650611L;

    public AgentBESA(String alias, StateBESA state, StructBESA structAgent, double passwd) {
        boolean indMove = true; //Indica que es de movimiento, se deduce si ya existe o no
        String aliasr = null;	//alais d ela reflection
        try {
            this.state = state;
            this.state.initGuards();
            this.structAgent = structAgent;
            this.alias = alias;
            this.passwd = passwd;
            AgentBESA.admLocal = AdmBESA.getInstance();

            aliasr = alias;
            // Si antes de registrar ya existe y aparte de todo es remoto, indica
            // que es un movimiento y al crear al agente no se debe publicar
            //cUANDO ES POR INTROSPECCI�N EL ALIAS NO ES EL ALIAS ES EL AGID
            if (AgentBESA.admLocal.getHandlerByAid(alias) == null) {
                //no es por movimiento
                indMove = false;
            } else if (AgentBESA.admLocal.getHandlerByAid(alias).getAgHandlerBESA().getLocation() == AgHandlerBESA.AG_LOCATION_LOCAL) {
                indMove = false;
            } else { //si es por movimiento entonces se debe crear y tiene referencia remota
                aliasr = AgentBESA.admLocal.erase(alias);
            }

            // Registrar agente en p�ginas blancas y obtener aid
            this.aid = AgentBESA.admLocal.registerLocalAgent(this, alias, indMove, aliasr);
            // Crear vector de comportamientos y arrancar canal
            this.behaviors = new ArrayList();
            this.channel = new ChannelBESA(this);
//	        channel.set_mobile(true);//es falso cuando arranca normal y true cuando es por movilidad,se deja true para probar
            // Asegurar que ciclo principal del run del canal arranca
            this.setAlive();
            // Crear hilo, arrancarlo y cederle el procesador para que arranque
            Thread thread = new Thread(this.channel);
            thread.start();
            // Sincronizac�n de arranque - Esperar setup completo del canal
            this.waitChannelReady();
            ReportBESA.debug(1, "[AgentBESA::AgentBESA] Despues del waitChannelReady");
            //Se hace la publicaci�n del nuevo agente en los otros contenedores
            //Se verifica si es distribuido para puvblicarlo en otros contenedores
            if (!admLocal.isCentralized()) {
                if (indMove == false) {
                    admLocal.publicagent(alias, this.aid);
                }
            }
        } catch (Exception e) {
        	e.printStackTrace();
            ReportBESA.error("[AgentBESA::AgentBESA] Error en instanciar el agente" + alias);
        }
    }

    public AgentBESA(AgentBESA old) {
    }

    public EventBESA createEvent(String evType, DataBESA data) {
        return new EventBESA(evType, this.aid, data);
    }

    /* A DEPRICATED */
    final static public void sendEvent(EventBESA ev, String aid) throws ExceptionBESA {
        // Obtener tarjeta del destinatario
        // hacia el futuro se puede implantar un cache
        AgHandler agHandler = admLocal.getHandlerByAid(aid);
        try {
            agHandler.sendEvent(ev);
        } catch (Exception e) {
        	e.printStackTrace();
            throw new ExceptionBESA("[AgentBESA::sendEvent(evc,aid)] ExceptionBESA:" + e);
        }
    }

    /*Se cambi� de protected a public porque se separ� en un paquete diferente
    17 enero 2006
     */
    public void sendEvent(EventBESA ev) throws ExceptionBESA {
        // Enviar un evento al agente, se usa la referencia interna del agente
        try {
            this.channel.getMbox().sendEvent(ev);
        } catch (Exception e) {
        	e.printStackTrace();
            throw new ExceptionBESA("[AgentBESA::senEvent(ev)] ExceptionBESA:" + e);
        }
    }

    abstract public void shutdownAgent();
    // Rutina de cierre del agente - liberar recursos - parar comportamientos
    // Invocada autom�ticamente al salir de la bucla infinita

    /**
     * @return  Returns the aid.
     * @uml.property  name="aid"
     */
    final public String getAid() {
        return aid;
    }

    /**
     * @return  Returns the alias.
     * @uml.property  name="alias"
     */
    final public String getAlias() {
        return alias;
    }

    /**
     * @return  Returns the state.
     * @uml.property  name="state"
     */
    final public StateBESA getState() {
        return state;
    }

    private synchronized void setAlive() {
        this.alive = Boolean.TRUE;
    }

    //antes privado
    public synchronized void resetAlive() {
        this.alive = Boolean.FALSE;
    }

    final public void kill(double passwd) {
        // Parar y terminar todos los comportamientos
        // Reset de alive para producir salida de la bucla while del canal
        // OJO    No debe llamarse desde los hilos de los comportamientos sino
        // puede generar bloqueos, dicho de otra forma no debe ser invocada
        // desde la funcion asociada a 	|una de las guardas internas del agente
        // Verificar passwd
        if (this.passwd == passwd) {
            // Acceso synchronized en la variable alive
            synchronized (alive) {
                if (this.isAlive().booleanValue()) {
                    // Sincronizac�n de arranque - Esperar setup completo del canal
                    this.getChannel().initBehBarrier(this.behaviors.size());
                    killBehaviors(passwd);
                    this.getChannel().waitBehBarrier();
                    //Eliminar behavior's del agente decentemente
                    for (int i = 0; i < this.behaviors.size(); i++) {
                        // Desasociar behavior al agente en el cual esta incluido
                        BehaviorBESA beh = (BehaviorBESA) this.behaviors.remove(i);
                        beh = null;
                    }
                    // Forzar finalizacion del hilo del canal y de los comportamientos
                    this.resetAlive();
                    //Envio el evento para desbloquear el canal
                    String evType = "KILL_BESA_AGENT";
                    try {
                        EventBESA ev = new EventBESA(evType, this.getAdmLocal().getPasswd());
                        this.sendEvent(ev);
                    } catch (Exception e) {
                    	e.printStackTrace();
                        ReportBESA.error("[BehaviorBESA:kill]:No send" + e);
                    }
                }
            } //fin synchronized
        } //fin if passwd
    }

    final public void move(double passwd) {
        // Parar y terminar todos los comportamientos
        // Reset de alive para producir salida de la bucla while del canal
        // OJO    No debe llamarse desde los hilos de los comportamientos sino
        // puede generar bloqueos, dicho de otra forma no debe ser invocada
        // desde la funcion asociada a 	|una de las guardas internas del agente
        // Verificar passwd
        if (this.passwd == passwd) {
            // Acceso synchronized en la variable alive
            synchronized (alive) {
                if (this.isAlive().booleanValue()) {
                    // Sincronizac�n de arranque - Esperar setup completo del canal
                    this.getChannel().initBehBarrier(this.behaviors.size());
                    killBehaviors(passwd);
                    this.getChannel().waitBehBarrier();
                    //Eliminar behavior's del agente decentemente
                    for (int i = 0; i < this.behaviors.size(); i++) {
                        // Desasociar behavior al agente en el cual esta incluido
                        BehaviorBESA beh = (BehaviorBESA) this.behaviors.remove(i);
                        beh = null;
                    }
                    // Forzar finalizacion del hilo del canal y de los comportamientos
                    this.resetAlive();
                    //Envio el evento para desbloquear el canal
                    String evType = "KILL_BESA_AGENT";
                    try {
                        EventBESA ev = new EventBESA(evType, this.getAdmLocal().getPasswd());
                        this.sendEvent(ev);
                    } catch (Exception e) {
                    	e.printStackTrace();
                        ReportBESA.error("[BehaviorBESA:kill]:No send" + e);
                    }
                }
            } //fin synchronized
        } //fin if passwd
    }

    final public int startBehaviors() {
        // Sincronizacion de barrera - Asegurar que comportamientos listos
        // Init de contador de la sincronizacion de barrera
        this.channel.initBehBarrier(behaviors.size());

        // Arranque de los hilos de los comportamientos asociados
        int count = 0;
        for (int i = 0; i < behaviors.size(); ++i) {
            BehaviorBESA ibeh = (BehaviorBESA) behaviors.get(i);
            Thread thread = new Thread(ibeh);
            ibeh.setThread(thread);
            thread.start();
            count++;
        }
        // Esperar que se ejecuten los setups de los behaviors
        ReportBESA.debug(1, "[AgentBESA::startBehaviors] Antes waitBehBarrier");
        this.channel.waitBehBarrier();
        ReportBESA.debug(1, "[AgentBESA::startBehaviors] Despues waitBehBarrier");
        return count;
    }

    final public void killBehaviors(double passwd) {
        // Forzar finalizacion de los hilos de los comportamientos asociados
        if (this.passwd == passwd) {
            for (int i = 0; i < behaviors.size(); ++i) {
                BehaviorBESA ibeh = (BehaviorBESA) behaviors.get(i);
                ReportBESA.debug(1, "[AgentBESA::killBehaviors] Antes de kill del Behavior:" + ibeh.getName());
                ibeh.kill();
                ReportBESA.debug(1, "[AgentBESA::killBehaviors] Despues de kill Behavior");
            }
        }
        System.out.println("La cantidad de esperas es de:" + this.getChannel().getbarrierCounter());
        while (this.getChannel().getbarrierCounter() > 0) {
            this.getChannel().signalBehBarrier();
        }

    }

    final public void joinBehaviors(double passwd) {
        // Sincronizacion de fin de los hilos de los comportamientos asociados
        if (this.passwd == passwd) {
            for (int i = 0; i < behaviors.size(); ++i) {
                BehaviorBESA ibeh = (BehaviorBESA) behaviors.get(i);
                try {
                    ibeh.getThread().join();
                } catch (InterruptedException ie) {
                    ReportBESA.error("[AgentBESA::joinBehaviors] " + ie.getMessage());
                }
            }
        }
    }

    final public synchronized Port bindGuard(GuardBESA guard) {
        // Se debe crear un puerto asociado a la guarda
        // Valida que no se ha creado un puerto para el evento de la guarda
        // Retorna null si ya existia un puerto para el mismo tipo de evento
        return channel.addPort(guard);
    }

    final public synchronized boolean unbindGuard(GuardBESA guard) {
        // Eliminar puerto asociado a la guarda
        // Valida primero que se ha creado un puerto para esta guarda
        // y que no hay ningun comportamiento asociado al puerto
        // Si hay aun algun comportamiento asociado entonces retorna falso
        return channel.removePort(guard);
    }

    final public synchronized void registerBehavior(BehaviorBESA beh) {
        // Registro automatico en la constructora del behavior
        // Incluir en vector de comportamientos evitando repeticion
        // Esta lista es util para sincronizaciones colectivas
        // Tambien se usa para matar automaticamente los comportamientos
        // cuando el agente muere
        if (!behaviors.contains(beh)) {
            behaviors.add(beh);
        }
    }

    final public synchronized void unregisterBehavior(BehaviorBESA beh) {
        // DesRegistro automatico despues de la bucla del behavior
        // Eliminar del vector de comportamientos
        // Purgar puertos
        behaviors.remove(beh);
        channel.purgePorts(beh);
    }

    final public synchronized GuardBESA registerGuard(BehaviorBESA beh, String evType) {
        // Asociar un comportamiento a una guarda que maneja un tipo de evento
        Port port = channel.findPort(evType);
        if (port != null) {
            port.bindBehavior(beh);
            return port.getGuard();
        }
        return null;
    }

    final public synchronized void unregisterGuard(BehaviorBESA beh, String evType) {
        // Desasociar un comportamiento a una guarda que maneja un tipo de evento
        Port port = channel.findPort(evType);
        if (port != null) {
            port.unbindBehavior(beh);
        }
    }

    final public synchronized Boolean isAlive() {
        return alive;
    }

    /**
     * @return  Returns the channel.
     * @uml.property  name="channel"
     */
    final public ChannelBESA getChannel() {
        return channel;
    }

    final public synchronized void signal() {
        this.notify();
    }

    //era private --nuevo
    public synchronized void waitChannelReady() {
        try {
            ReportBESA.debug(1, "[AgentBESA::waitChannelReady()] Antes del wait");
            this.wait();
        } catch (InterruptedException ie) {
        	ie.printStackTrace();
            ReportBESA.error("[AgentaBESA::waitChannelReady()]" + ie.getMessage());
        }
    }

    public static void initAdmLocal(AdmBESA adm) {
        // init de la var estatica con control tipo singleton
        if (admLocal == null) {
            admLocal = adm;
        }
    }

    final public AdmBESA getAdmLocal() {
        return admLocal;
    }

    public void init() {
        try {
            ReportBESA.debug(1, "[AgentBESA::Setup] Inicia setup");
            structAgent.buildAgentStruct(this);
        } catch (Exception e) {
        	e.printStackTrace();
            ReportBESA.error("[AgentBESA::Setup] Error al inicializar setup");
        }
    }

    public boolean verifyPasswd(double passwd) {
        if (this.passwd == passwd) {
            return true;
        } else {
            return false;
        }
    }

    //Bloquea de forma logica el canal
    public synchronized void waitChannel() {
        try {
            ReportBESA.debug(1, "[AgentBESA::waitChannel()] Antes del bloqueo logico del canal");
            this.getChannel().esperar();
            System.out.println("[AgentBESA::waitChannel()] Despues del bloquo lgico del canal");
        } catch (Exception ie) {
        	ie.printStackTrace();
            ReportBESA.error("[AgentaBESA::waitChannel()]" + ie.getMessage());
        }
    }

    abstract public void setupAgent();
    // Rutina de arranque - invocada autom�ticamente antes de la bucla infinita
    // Estado ya fue inicializado por constructora del agente
    // Crear las guardas - bind automatico
    // Crear comportamientos - registro automatico
    // Registrar servicios del agente

    //  abstract public void setup_mobile();
    //Retorna el estado del agente
    public StateBESA getStateAgent() {
        return state;
    }

    //Retorna la estructura del agente
    /**
     * @return  Returns the structAgent.
     * @uml.property  name="structAgent"
     */
    public StructBESA getStructAgent() {
        return structAgent;
    }

    //idea
    public void setReferenceState(Object stateAgent) {
        stateAgentSpecific = stateAgent;
    }

    protected double getPassword() {
        return this.passwd;
    }
    /**
     * @uml.property  name="stateAgentSpecific"
     */
    private Object stateAgentSpecific; //idea
    private static AdmBESA admLocal;
    /**
     * @uml.property  name="passwd"
     */
    private double passwd;
    /**
     * @uml.property  name="aid"
     */
    private String aid = null;
    /**
     * @uml.property  name="alive"
     */
    private Boolean alive;
    /**
     * @link  aggregation
     * @associates  BESA.Agent.BehaviorBESA
     * @uml.property  name="behaviors"
     * @uml.associationEnd  multiplicity="(0 -1)" inverse="ag:BESA.Agent.BehaviorBESA"
     */
    private ArrayList behaviors;
    /**
     * @uml.property  name="alias"
     */
    private String alias;
    /**
     * @uml.property  name="state"
     * @uml.associationEnd  multiplicity="(1 1)"
     */
    protected StateBESA state;
    /**
     * @uml.property  name="channel"
     * @uml.associationEnd  multiplicity="(1 1)" inverse="ag:BESA.Agent.ChannelBESA"
     */
    private ChannelBESA channel;
    /**
     * @uml.property  name="structAgent"
     * @uml.associationEnd  multiplicity="(1 1)"
     */
    private StructBESA structAgent;
}
